大家好,我是 Yubin
這篇文章會跟大家介紹如何實作上傳檔案到 Object Storage。
並在本地端建立相應的開發環境,使用的 Object Storage 是 Minio,要上傳到 AWS S3 也是相同作法。
要上傳檔案,要先讓 Fastify App 可以支援 multipart 格式的 Content-Type,這邊使用 @fastify/multipart
這個官方 Plugin 來實作。
對於使用 @fastify/multipart
不熟悉的朋友,可以參考 Fastify101: Upload File。
MinIO 是一款非常熱門的 Object Storage。
說到 Object Storage,許多人可能耳熟能詳的是 AWS S3 服務,但對於公司內部服務的使用或開發人員本地開發環境的架設來說,Minio 是一個非常棒的選擇。
Minio 相容於 S3 的 API,意思是就算你的服務要接的 Object Storage 是 S3,也可以在本地端使用 MinIO 來進行開發測試。
除了相容於 S3,MinIO 是 100% 開源 (可商業使用的 GNU AGPL v3
授權),且支援部屬到 Kubernetes 或其他公私有雲服務商上。
為了開發的時候方便,我們在自己的本地端上起一個 MinIO 的環境,根據不同作業系統可以參考 MinIO 官方文件 來進行安裝。
當然,為了快速方便,這邊還是一樣使用 Container 來操作。
打開 terminal 敲入:
docker run -p 9000:9000 -p 9001:9001 --name=minio-server \
quay.io/minio/minio server /data \
--console-address ":9001"
如果使用 podman,把
docker run
換成podman run
即可。
這裡使用的是 MinIO 官方的 image: quay.io/minio/minio
。
MinIO 的 Port,預設會使用 9000
及 9001
。
9000
是 API Port,也就是程式用來溝通用的 Port。9001
是 Console Port,這邊的 Console 是指給人看的管理介面。
起好 Container 後,打開瀏覽器 localhost:9001
。
看到登入表單,輸入預設帳號密碼 minioadmin
/ minioadmin
就可以登入。
補充,有些版本的 image 不允許使用預設帳號密碼登入。
可以透過起 Container 的時候加上環境變數MINIO_ROOT_USER
和MINIO_ROOT_PASSWORD
來更改 root 帳號密碼。
登入之後就可以看到 MinIO 的管理頁面。
在 MinIO (或 S3) 的使用上,每個檔案都會放在某個 Bucket 底下,所以我們要先建立 Bucket。
我們的目的只是測試開發用,可以自己取喜歡的 Bucket 名字。
要特別注意的是,Bucket 的名稱不能使用英文大寫來命名。
建好 Bucket 之後,我們來新增 MinIO 的使用者。
這邊的使用者會設定相應的權限,也就是可以操作這座 MinIO Server 的使用者。
除了個別設定使用者外,也可以設定 Group 以及 Policy。
權限的設定攸關資料的隱私及伺服器的安全,請妥善閱讀文件進行相應的設定。
要建立開發測試用的使用者,我們這邊可以自己設定該使用者的 Username 及 Password。
權限部分勾選 readwrite
就足夠了。
建立好 MinIO User 後,就可以開始設定程式端的連接。
要使用 JavaScript 與 MinIO 進行互動,可以透過官方的套件:minio-js。
可以透過 npm 來安裝:
npm i minio
因為我們使用 Typescript 進行開發,要另外安裝 Type 定義檔:
npm i -D @types/minio
接著在程式裡面設定 MinIO Client,帶入要連接的 MinIO Server 資訊。
import * as minio from 'minio'
const minioClient = new minio.Client({
endPoint: 'localhost',
port: 9000,
useSSL: false,
accessKey: 'user01',
secretKey: 'user01password'
})
透過 minio-js 產生 minioClient 物件。
endPoint
,是要連線的 MinIO Server 的位置,因為我們這邊起在本機,所以用 localhost
即可。port
,目標 MinIO Server 使用的 API Port,預設使用的 Port 為 9000
。useSSL
,如果目標 MinIO Server 有透過 SSL 加密連線,要設定為 true
。accessKey
,這邊填入 MinIO User 的 User Name 即可。secretKey
,填入 MinIO User 的 Password。補充,如果使用 AWS S3,就會給你連線用的 accessKey 及 secretKey,而不會是像上面範例輸入使用者的帳號密碼。
接著就可以透過 minioClient 來使用 MinIO API 來與 MinIO Server 進行互動。
putObject()
是 MinIO Client 的方法,可以把檔案上傳到 MinIO。
await minioClient.putObject(
bucketName,
filename,
fileBuffer
)
回傳的是 Promise 物件,可以使用 async/await
style 或 callback style 來寫。
也可以帶入 metaData 作為附加資訊。
const metaData = {
key01: 'value01'
}
await minioClient.putObject(bucketName, filename, fileBuffer, metaData)
以下程式定義了 POST /uploads
這個 Endpoint,Client 端把檔案送過來後,透過 MinIO Client 上傳到 MinIO Server。
使用 @fastify/multipart,可以參考 Fastify101: Upload File。
import fastify, { FastifyInstance } from 'fastify'
import * as minio from 'minio'
const server: FastifyInstance = fastify()
const minioClient = new minio.Client({
endPoint: 'localhost',
port: 9000,
useSSL: false,
accessKey: 'user01',
secretKey: 'user01password'
})
const bucketName = 'bucket01'
server.post('/uploads', async (request, reply) => {
try {
const data = await request.file()
if (data) {
const filename = data.filename
const fileBuffer = await data.toBuffer()
const metadata = {
key01: 'value01'
}
// upload to minio
await minioClient.putObject(bucketName, filename, fileBuffer, metadata)
return reply
.status(201)
.send({ message: `Successfully upload file: ${filename}` })
} else {
return reply.status(400).send({ message: `Failed to upload file` })
}
} catch (error) {
return reply.status(500).send({ error })
}
})
定義好 route 後,把程式跑起來。
透過 Postman 幫我們打一個 POST 的 Request,並帶上檔案。
看到成功的回應。
接著我們打開 MinIO Console (http://localhost:9001
)。
可以在 Bucket 的頁面按 Broswer
瀏覽該 Bucket 內的檔案。
看到剛才上傳的檔案,也可以在這邊對檔案進行操作。
也可以觀察到,剛才在程式中的 .putObject()
有帶入了 Metadata:
const metadata = {
key01: 'value01'
}
上傳到 MinIO 上會以這樣的形式呈現。
MinIO 是個優秀的 Object Storage,透過 Container 我們也可以快速的建立好開發環境。
官方維護的 minio-js 的操作也相當直覺。
如上述提到的,使用者權限的設定很重要,某個使用者只能對某個 Bucket 進行操作,或只有讀取的權限等。跟資料有關的權限都要謹慎規劃。
把檔案統一交由 Object Storage 管理,而不是存在某台機器的 Local File System 上,是 Cloud Native 中相當常見的做法,不會因為 App 本身的水平擴展而造成資料存取上的問題。
上述範例程式是資料上傳進來,以 Buffer 的形式直接交給 MinIO。
如果要上傳本地端的檔案到 MinIO,可以使用 fPutObject
,相關資訊可以參考官方文件的範例程式。
本篇文章的完整程式可以參考 GitHub。